Skip to content

fix(runtime,metadata): stamp env_local as the default local environment id - #15200

Merged
os-warren merged 3 commits into
mainfrom
claude/issue-13366-env-local-stamp-rename
Sep 4, 2026
Merged

fix(runtime,metadata): stamp env_local as the default local environment id#15200
os-warren merged 3 commits into
mainfrom
claude/issue-13366-env-local-stamp-rename

Conversation

@os-litant

Copy link
Copy Markdown
Collaborator

Fixes #13366

Implements the domain:cli PM ruling in comment 5495605293option (a), in the narrow form — and nothing beyond its six-point scope.

What changes

The v5.0 project to environment rename changed the default local environment id and shipped that change on the surfaces most people meet. It never reached two stamps:

site before after
packages/runtime/src/standalone-stack.ts — the createStandaloneStack boot fallback proj_local env_local
packages/metadata/src/plugin.ts — the placeholder that fills the environment-artifact validation envelope for a bare definition proj_local env_local

Two comments in standalone-stack.ts narrate what this stack stamps (one of them present tense, twenty lines above the line it describes). They are converted in the same edit, because leaving them would have manufactured a fresh declared != enforced inside the file this PR exists to make consistent. One of the two also carried a stale line anchor, line ~515 for what is now line 567; corrected in passing since the sentence was being edited anyway.

Published surface, and what an embedder observes (Clause-② statement)

The changed surface is createStandaloneStack / createDefaultHostConfig (@objectstack/runtime), on the DEFAULT path only — no environmentId in the config, no OS_ENVIRONMENT_ID in the environment.

  • Before: that boot's kernel is stamped proj_local. The id is a row-scope key inside ObjectQLPlugin and reaches sys_metadata.environment_id, the X-Environment-Id header, and MetadataPlugin options.
  • After: the same boot is stamped env_local, which is what packages/cli/CHANGELOG.md and content/docs/deployment/cli.mdx have promised as the default since the rename.
  • Migration for an install that already has rows under the old id: set environmentId: 'proj_local' in the config, or OS_ENVIRONMENT_ID=proj_local in the environment. Both still outrank the default — that precedence is unchanged and is now pinned.

Two audiences reach it, and naming only the first would understate the change:

  1. a host that calls createStandaloneStack / createDefaultHostConfig directly;
  2. a bare os serve — one not spawned by os dev / os start. serve.ts sets no OS_ENVIRONMENT_ID for its own boot; it only READS one, to name the runtime state file. So a bare os serve used to run a kernel stamped proj_local while publishing runtime.env_local.json beside it. The two now agree, which is the split-brain the phase-1 census described closing.

Who does NOT observe it. A boot started by os dev / os start never reached the changed line: those commands export OS_ENVIRONMENT_ID=env_local into the child process (dev.ts:380, start.ts:409), and the fallback yields to it. That is why the mismatch could hold for a whole major line without one test going red.

Where the id crosses to closed cloud. The repo:cloud seat swept objectstack-ai/cloud@9b6abe0f2fd5 on the seam card: 14 proj_local hits, zero env_local, zero production-code comparisons on either literal; the one production consumer (kernel-resolver.ts:256-266) reads the id opaquely into envRegistry.resolveById(...). That repo is unreachable from this container, so it is reported as an inherited given, not as something this PR re-measured.

Deliberately NOT changed (ruling points 2, 3, 4 and 6)

  • packages/cloud-connection/src/cloud-connection-plugin.ts keeps its dual accept of both spellings. It guards persisted OS_ENVIRONMENT_ID=proj_local configs in the wild, exactly as its comment says. It also puts both literals in the same arm, so it could not have changed outcome either way.
  • packages/runtime/src/package-state-store.ts keeps its separate 'default' sanitiser fallback — renaming it would re-key persisted package-disable state files for a cosmetic gain.
  • content/docs/deployment/seed-tenancy-repair.mdx is untouched: its text is true history, not a description of a live broken gate.
  • packages/cli/src/commands/serve.ts runtime-file derivation is untouched. Supervisors pin runtime.env_local.json as a contract, and a contract move must not ride a spelling cleanup.

Evidence

Red-first, before converting. The new pin was written and run against the unconverted tree, and it failed with the value it exists to keep out:

AssertionError: expected { metadata: 'proj_local', ... } to deeply equal { metadata: 'env_local', ... }
+   "metadata": "proj_local",
+   "objectql": "proj_local",
 Tests  1 failed | 2 passed (3)

So the pin reaches the code this PR changes, rather than re-declaring a constant. It reads the id off the two plugin instances the stack composes — the last point at which the stamped value is still identifiable before it dissolves into row scoping — and covers the precedence above it.

Green after, at 389a4852:

run result
@objectstack/runtime full suite 221 files, 3163 tests passed
@objectstack/metadata full suite 46 files, 705 tests passed
guard set — serve-bound-port-publication, serve-bound-port-publish-order, serve-publishes-bound-port.e2e 3 files, 31 tests passed
guard set — publish-smoke-port-collision (@objectstack/spec) 1 file, 8 tests passed
guard set — cloud-connection-plugin (pins the dual accept itself) 1 file, 12 tests passed
CLI standalone-stack consumers — platform-migrations-arming, three schema-migrate integrations, graft-runtime-hooks, merge-boot-config, driver-vocabulary-parity, serve-host-config-security-registrar.pin 8 files, 102 tests passed

The guard set was named in the phase-1 census as the tests that would go red if the CLI default moved. This PR does not move the CLI default, and all five stayed green — the blast radius is what the census said it was.

On the metadata site specifically, stated rather than faked. EnvironmentArtifactSchema declares environmentId: z.string() with no format constraint, and _parseAndRegisterArtifact discards the parse result (metadata = def). The literal there is a placeholder that satisfies a required key on a validation call whose output is thrown away, so it has no observable output and is given no behavioural pin. Writing one would have asserted a copy of the constant against itself.

Out of scope, filed separately

Converting the stamp leaves five comments in four other packages describing the standalone stamp with the old spelling. They are outside the ruling's scope, so they are recorded on their own card rather than fixed here.


🤖 Generated with Claude Code

https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N


Generated by Claude Code

…nt id

Finish the v5.0 `project` to `environment` rename at the two sites it never
reached. `packages/cli/CHANGELOG.md` records "Default local env id:
`proj_local` -> `env_local`" and `content/docs/deployment/cli.mdx` documents
`env_local`, while `createStandaloneStack` and `MetadataPlugin` still stamped
`proj_local` — declared != enforced on a published default.

- `packages/runtime/src/standalone-stack.ts`: the boot fallback, plus the two
  in-file comments that narrate what this stack stamps (one of them also
  carried a stale line anchor, ~515 -> ~567).
- `packages/metadata/src/plugin.ts`: the placeholder that fills the
  `EnvironmentArtifactSchema` validation envelope for a bare definition.

Adds the pin the value never had: the id is asserted where it LANDS, on the
`MetadataPlugin` and `ObjectQLPlugin` instances the stack composes, together
with the precedence above it (`cfg.environmentId` > `OS_ENVIRONMENT_ID` >
default).

`cloud-connection`'s dual accept and `package-state-store`'s separate
`'default'` sanitiser fallback are deliberately untouched.

Part of #13366

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N
Part of #13366

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N
…udience

`os serve` sets no OS_ENVIRONMENT_ID for its own boot — it only reads one to
name the runtime state file — so a bare `os serve` reaches the changed default
too, not only a direct `createStandaloneStack` embedder. Measured at
`serve.ts:524` (read-only) against `dev.ts:380` / `start.ts:409` (which export).

Part of #13366

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N
@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/metadata, @objectstack/runtime, touching 5 documentable anchor(s).

6 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/api/environment-routing.mdx (via createStandaloneStack (symbol, a top-level function))
  • content/docs/data-modeling/drivers.mdx _(via createStandaloneStack (symbol, a top-level function), env_local (literal, a string literal in StandaloneStackConfigSchema; a string literal in parseAndRegisterArtifact; a string literal in createStandaloneStack))
  • content/docs/deployment/cli.mdx _(via env_local (literal, a string literal in StandaloneStackConfigSchema; a string literal in parseAndRegisterArtifact; a string literal in createStandaloneStack))
  • content/docs/deployment/seed-tenancy-repair.mdx _(via proj_local (literal, a string literal in StandaloneStackConfigSchema; a string literal in parseAndRegisterArtifact; a string literal in createStandaloneStack))
  • content/docs/deployment/single-project-mode.mdx _(via createStandaloneStack (symbol, a top-level function), env_local (literal, a string literal in StandaloneStackConfigSchema; a string literal in parseAndRegisterArtifact; a string literal in createStandaloneStack))
  • content/docs/plugins/index.mdx (via createStandaloneStack (symbol, a top-level function))
What this run could not see
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 30 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 919beca43b2901320cea39afd912b7f662ddd3b1packageMentionDocs.

Which tree this was computed on

This run read content/docs from 4bff6af5648983cad813518ac64d6aaaf82c8517 — the merge of head 389a485293befeb7e122cc784575d9ca473e1297 into base 919beca43b2901320cea39afd912b7f662ddd3b1, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 4bff6af5648983cad813518ac64d6aaaf82c8517 && git checkout 4bff6af5648983cad813518ac64d6aaaf82c8517
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 919beca43b2901320cea39afd912b7f662ddd3b1 389a485293befeb7e122cc784575d9ca473e1297 && git checkout -B drift-repro 919beca43b2901320cea39afd912b7f662ddd3b1 && git merge --no-ff 389a485293befeb7e122cc784575d9ca473e1297

node scripts/docs-audit/affected-docs.mjs --json 919beca43b2901320cea39afd912b7f662ddd3b1

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 919beca43b2901320cea39afd912b7f662ddd3b1 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

Copy link
Copy Markdown
Collaborator

Contract review PASS at CONTRACT_REVIEW_TIER — director seat (objectstack #12708, session_01LsEjuNMPitCHwEfYftZ1um), 2026-09-04. Verdict with the derived judgments, the semver ruling (the dev's open question: A, patch stands) and the boundary flags is on the card: #13366 comment 5536870980. Tier fuse read before the review (get_session: claude-fable-5-1 served), so the maintainer's parking ruling 5536346671 is satisfied. Not governed (check-governed-merges --test: 0 of 4 paths). needs:contract-review removed here (read-modify-write, other labels untouched), PR marked ready, squash auto-merge armed; this seat follows it to MERGED, and #13366 closes on Fixes.


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

3 participants